package com.pingan.haofang.searchcloud.api.query.parser.visitor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.pingan.haofang.searchcloud.api.matedata.FieldMeta;
import com.pingan.haofang.searchcloud.api.matedata.IndexMeta;
import com.pingan.haofang.searchcloud.api.matedata.constant.SQLConstant;
import com.pingan.haofang.searchcloud.api.query.parser.constant.SqlVisitorStep;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.FunctionFactory;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.SqlNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.ColumnNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.TextNode;
import com.pingan.haofang.searchcloud.api.query.parser.exception.SqlParseException;

import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.DoubleValue;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.SignedExpression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.arithmetic.Division;
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
import net.sf.jsqlparser.expression.operators.arithmetic.Subtraction;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.schema.Column;

/**
 * ClassName: ConditionNodeVisitor <br>
 * Function: 查询节点解析
 *
 * @author luyi
 * @version 1.0.0
 * @date 20150523
 * @since 1.0.0
 */
public class ConditionNodeVisitor extends AbstractExpressionVisitor {

    /**
     * SQL节点
     */
    private SqlNode conditionNode;

    /**
     * 索引meta信息
     */
    private IndexMeta meta;

    /**
     * 访问器当前步骤
     */
    private SqlVisitorStep step;

    /**
     * 抓取节点集合
     */
    private Map<String, SqlNode> selectNodeMap;

    public ConditionNodeVisitor(SqlVisitorStep step, IndexMeta meta) {
        this.meta = meta;
        this.step = step;
    }

    public ConditionNodeVisitor(SqlVisitorStep step, IndexMeta meta, Map<String, SqlNode> selectNodeMap) {
        this.meta = meta;
        this.step = step;
        this.selectNodeMap = selectNodeMap;
    }

    @Override
    public void visit(DoubleValue doubleValue) {
        conditionNode = new TextNode(doubleValue.getValue());
    }

    @Override
    public void visit(LongValue longValue) {
        conditionNode = new TextNode(longValue.getValue());
    }

    @Override
    public void visit(StringValue stringValue) {
        conditionNode = new TextNode(stringValue.getValue());
    }

    @Override
    public void visit(SignedExpression signedExpression) {
        conditionNode = new TextNode(signedExpression.toString());
    }

    @Override
    public void visit(Addition addition) {
        visitBinaryExpression(addition);
    }

    @Override
    public void visit(Division division) {
        visitBinaryExpression(division);
    }

    @Override
    public void visit(Multiplication multiplication) {
        visitBinaryExpression(multiplication);
    }

    @Override
    public void visit(Subtraction subtraction) {
        visitBinaryExpression(subtraction);
    }

    @Override
    public void visit(Column tableColumn) {
        if (tableColumn.getColumnName().startsWith("\"") && tableColumn.getColumnName().endsWith("\"")) {
            conditionNode =
                    new TextNode(tableColumn.getColumnName().substring(1, tableColumn.getColumnName().length() - 1));
        } else {
            FieldMeta fieldMeta = meta.getFiledMetas().get(tableColumn.getColumnName());
            if (fieldMeta == null) {

                // 如果是where条件，则需要进一步判断是否是保留字段
                if (step == SqlVisitorStep.WHERE && SQLConstant.HF_KEY.containsKey(tableColumn.getColumnName())) {

                    FieldMeta hfKeyFieldMeta = SQLConstant.HF_KEY.get(tableColumn.getColumnName());
                    ColumnNode column = new ColumnNode();
                    column.setColumnName(hfKeyFieldMeta.getName());
                    column.setSchema(tableColumn.getTable().getName());
                    column.setMeta(hfKeyFieldMeta);
                    conditionNode = column;
                } else if (step == SqlVisitorStep.ORDERBY) { // 针对order by特殊处理，需要兼容别名
                    if (!selectNodeMap.containsKey(tableColumn.getColumnName())) {
                        throw new SqlParseException(
                                "cannot found available meta for alias: " + tableColumn.getColumnName());
                    }
                    conditionNode = selectNodeMap.get(tableColumn.getColumnName());
                } else {
                    throw new SqlParseException(
                            "cannot found available meta for column: " + tableColumn.getColumnName());
                }
            } else {
                ColumnNode column = new ColumnNode();
                column.setColumnName(tableColumn.getColumnName());
                column.setSchema(tableColumn.getTable().getName());
                column.setMeta(fieldMeta);
                conditionNode = column;
            }
        }
    }

    @Override
    public void visit(Function function) {
        com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.function.Function func =
                FunctionFactory.getFunction(function.getName(), false);
        if (func == null) {
            throw new SqlParseException("cannot support function: " + function.toString());
        }

        if (function.getParameters() != null) {
            int size = Optional.ofNullable(function).map(Function::getParameters)
                    .map(ExpressionList::getExpressions)
                    .map(List::size).orElse(10);
            List<SqlNode> params = new ArrayList<>(size);
            for (Expression expr : function.getParameters().getExpressions()) {
                ConditionNodeVisitor visitor = new ConditionNodeVisitor(step, meta);
                expr.accept(visitor);
                params.add(visitor.getConditionNode());
            }
            func.setParams(params);
        }

        conditionNode = func;
    }

    private void visitBinaryExpression(BinaryExpression expr) {
        expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
    }

    public SqlNode getConditionNode() {
        return conditionNode;
    }

}
